package com.corticon.dialogs.service;

import com.corticon.dialogs.dao.UserGroupDAO;
import com.corticon.dialogs.dao.hibernate.UserHibernateDAO;
import com.corticon.dialogs.model.User;
import com.corticon.dialogs.model.UserGroup;
import com.corticon.dialogs.util.security.CustomUserDetails;
import com.corticon.dialogs.util.security.CustomUserDetailsImpl;
import com.corticon.dialogs.util.security.PasswordNotMatchException;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Session;
import org.springframework.beans.BeansException;
import org.springframework.security.AuthenticationException;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.DisabledException;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.context.SecurityContext;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.context.SecurityContextImpl;
import org.springframework.security.providers.ProviderManager;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/**
 *
 * @author Minghui Zheng
 */
@Service("portalUserService")
public class PortalUserServiceImpl implements PortalUserService {

    private static final Log logger = LogFactory.getLog(PortalUserServiceImpl.class);
    private static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";//this string defined by spring security
    private static final String PORTAL = "Portal";//group name
    private static final String ROLE_PORTAL_USER = "ROLE_PORTAL_USER";//role name
    
    @Resource
    private ProviderManager providerManager;
    @Resource
    private UserHibernateDAO userHibernateDAO;
    @Resource
    private UserGroupDAO userGroupDAO;

    public void setUserHibernateDAO(UserHibernateDAO userHibernateDAO) {
        this.userHibernateDAO = userHibernateDAO;
    }

    @Override
    public void signInUser(User user, HttpServletRequest request) {
        Session session = userHibernateDAO.getSession();
        //Set new user role as "ROLE_PORTAL_USER".
        Set<String> permissions = new HashSet<String>();
        permissions.add(ROLE_PORTAL_USER);
        user.setPermissions(permissions);
        //set new user group as "Portal"
        UserGroup portalGroup = userGroupDAO.findById(PORTAL);
        user.setUserGroup(portalGroup);
        session.save(user);
        //flush before login
        session.flush();
        userLogin(user, request);
    }

    @Override
    public void resetPassword(User pass) {
        User user = userHibernateDAO.findById(pass.getUsername());
        user.setPassword(pass.getPassword());
        userHibernateDAO.saveOrUpdate(user);
    }

    /**
     * Create a new SecurityContext, 
     * and create a Authentication use the user infomation, 
     * then put the Authentication into SecurityContext.
     * @param user
     * @param session
     */
    @Override
    public User userLogin(User guest, HttpServletRequest request) throws AuthenticationException {

        logger.debug("--------------------------------Do authentication.");

        User user = null;
        try {
            user = this.checkAuthentication(guest);
        } catch (BadCredentialsException be) {
            throw be;
        }


        if (user == null) {
            return user;
        }
        HttpSession httpSession = request.getSession(true);

        SecurityContext context = (SecurityContext) httpSession.getAttribute(
                SPRING_SECURITY_CONTEXT_KEY);

        // Create a new SecurityContext if there is not one in HttpSession.
        if (context == null) {
            context = new SecurityContextImpl();
        }

        try {
            //get user permissions
            Set<String> permissions = user.getPermissions();
            //Construct GrantedAuthority, will used as parameter for Constructing Authentication.
            GrantedAuthority[] grantedAuthorities = new GrantedAuthority[permissions.size()];
            int roleCount = 0;
            for (String role : permissions) {
                grantedAuthorities[roleCount] = new GrantedAuthorityImpl(role);
                roleCount++;
            }
            // Create user details
            CustomUserDetails userDetails = new CustomUserDetailsImpl(user.getUsername(), user.
                    getPassword(), user.isEnabled(),
                    true, true, true, grantedAuthorities, user.getUserGroup().getName());

            //Create authentication
            UsernamePasswordAuthenticationToken authentication =
                    new UsernamePasswordAuthenticationToken(userDetails, user.getPassword(),
                    grantedAuthorities);
            //set userDetails
            authentication.setDetails(userDetails);
            //do authentication
            providerManager.doAuthentication(authentication);

            logger.debug("--------------------------------------Finish do automatic authentication.");

            //put authentication into security context
            context.setAuthentication(authentication);
            //save security context into context holder
            SecurityContextHolder.setContext(context);
            //put context into httpsession.
            httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, context);

        } catch (DisabledException de) {

            //re-throw the DisabledException in order to set error message.
            throw new DisabledException("This account has been disabled, please contact your Administrator.");

        } catch (BeansException e) {
            logger.error("Could not get bean from web application context: ", e);
        }
        return user;//pass login
    }

    /**
     * Check username and password, if both pass, return user. 
     * Otherwise, return null, and throw {@link UsernameNotFoundException} or {@link PasswordNotMatchException}.
     * @param guest The guest want to login.
     * @return user The actual user or null if login failed.
     * @throws {@link BadCredentialsException}
     */
    private User checkAuthentication(User guest) throws BadCredentialsException {
        //get user if username correct
        User user = userHibernateDAO.findById(guest.getUsername());

        if (user == null) {//if there is no such user.
            throw new UsernameNotFoundException("You have entered an invalid username; please re-enter it.");

        } else if (!guest.getPassword().equals(user.getPassword())) {//if password does not match

            user = null;
            throw new PasswordNotMatchException("The password you entered does not match; please re-enter it.");

        }

        return user;
    }
}
